(function(global) {
    let target = null;
    const dictionary = {
        nouns: {},
        verbs: {},
        verbNames: [],
        keywords: [],
    };
    let stateMachine = null;
    const numbers = {
        a: 1,
        one: 1,
        two: 2,
        three: 3,
        four: 4,
        five: 5,
        six: 6,
        seven: 7,
        eight: 8,
        night: 9,
        ten: 10,
        eleven: 11,
        twelve: 12,
    };

    function _replaceNumbers() {
        const args = Array.from(arguments);
        return args.map(item => numbers[item] || item);
        // return Array.prototype.map.call(arguments, function(arg) {
        //     console.log('innumber replace', arg, numbers, numbers[arg])
        //     return numbers[arg] || arg;
        // })
    }

    function _findNounByVerb(verb) {
        if(Array.isArray(dictionary.verbNames[verb]) && dictionary.verbNames[verb].length === 1) {
            return dictionary.verbNames[verb][0];
        }
        if(dictionary.verbNames[verb] === 'target') {
            return target;
        }
        return undefined;
    }

    function buildDone(handle) {
        return function() {
            stateMachine = null;
            handle(dictionary);
        }
    }

    /**
     * Sample input:
     * sort employee list by name
     * sort by name
     * filter by name equals abc
     */   
    const translate = function (input) {
        console.log('start analysis:', input)
        // separate input by words into an array
        const words = input.replace(/[^ a-z]/ig, '').split(' ').filter(Boolean);
        // console.log('got words:', words)

        const allVerbNames = Object.keys(stateMachine || {}).concat(Object.keys(dictionary.verbNames));
        let matchVerb = '';
        let verbIndex = -1;
        
        // Find first valid verb
        for(let i = 0, len = words.length; i < len; i++) {
            let word = words[i];
            matchVerb = allVerbNames.find(verb => verb.toLocaleLowerCase() === word.toLocaleLowerCase());

            if(matchVerb) {
                verbIndex = i;
                break;
            }
        }

        if(!matchVerb) {
            console.log('Could not find match verb, failed.');
            return;
        }
        console.log('Found verb:', matchVerb);


        // Find valid noun name after verb
        const maybeNoun = words.slice(verbIndex + 1);
        const nounNames = Object.keys(dictionary.nouns);
        let matchNounName = '';
        let matchNoun;
        let nounIndex = -1;

        for(let i = 0, len = maybeNoun.length; i < len; i++) {
            let word = maybeNoun[i];
            matchNounName = nounNames.find(verb => verb.toLocaleLowerCase() === word.toLocaleLowerCase());

            if(matchNounName && typeof dictionary.nouns[matchNounName][matchVerb] === 'function') {
                nounIndex = i;
                break;
            }
        }

        matchNounName = matchNounName || _findNounByVerb(matchVerb)
        matchNoun = dictionary.nouns[matchNounName];

        if(!matchNoun) {
            console.log('Could not find match noun.');
        }
        console.log('Found noun:', matchNounName);

        // Find arguments after noun or verb
        let maybeArguments =  maybeNoun.slice(nounIndex + 1);
        maybeArguments = _replaceNumbers(maybeArguments)
        console.log('===> args  ', maybeArguments);

        // If state machine off and keyword, run and build state machine, maybe state machine equals return value of keyword
        let wrapper = null;
        if(stateMachine) {
            if (matchVerb === 'done') {
                stateMachine.done();
                return;
            }
            wrapper = typeof stateMachine[matchVerb] === 'function' 
            ? stateMachine[matchVerb] 
            : stateMachine.ing(run);
        } else if(dictionary.keywords.includes(matchVerb)) {
            wrapper = (input) => {
                const result = run(input);
                stateMachine = {
                    ...result,
                    done: buildDone(result.done),
                };
            }
        } else {
            wrapper = run;
        }

        // Find exactlly verb in nouns or global
        let action = (matchNoun && matchNoun[matchVerb]) || dictionary.verbs[matchVerb];
        const runInfo = {
            action: action,
            noun: matchNoun,
            args: maybeArguments,
        };
        // Use wrapper to run action with arguments
        // console.log(`run ${matchVerb} in ${matchNounName} by ${maybeArguments}`);
        wrapper(runInfo);

        return runInfo;
    };

    const run = function({ action, noun, args }) {
        return action.bind(noun || global)(...args); 
    };
    
    const bootstrap = function (keywords, nouns, verbs) {
        // initial dictionary
        dictionary.nouns = Object.assign({}, nouns);
        dictionary.verbs = Object.assign({}, keywords, verbs);
        dictionary.keywords = Object.keys(keywords);
        
        const allVerbs = Object.assign({}, ...Object.keys(dictionary.verbs).map(verb => ({
            [verb]: [global],
        })));
        Object.keys(nouns).forEach(noun => {
            Object.keys(nouns[noun])
                .filter(attr => typeof nouns[noun][attr] === 'function')
                .forEach(verb => {
                    allVerbs[verb] = allVerbs[verb] || [];
                    if (!allVerbs[verb].includes(noun)){
                        allVerbs[verb].push(noun);
                    }
                })
        });
        console.log('all register verbs', allVerbs);
        dictionary.verbNames = allVerbs;
    }

    global.transpiler = {
        bootstrap: bootstrap,
        translate: translate
    };
})(window);